﻿using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using System;
using System.Collections.Generic;
using System.Drawing;

namespace MaiYao
{
    public class Hsv
    {
        public double h;
        public double s;
        public double v;
    }

    class 图像
    {
        private static Image<Bgr, byte>[] 大地图数字数组 = new Image<Bgr, byte>[] {
        Properties.Resources.d_0.ToImage<Bgr, byte>(), Properties.Resources.d_1.ToImage<Bgr, byte>(), Properties.Resources.d_2.ToImage<Bgr, byte>(),
        Properties.Resources.d_3.ToImage<Bgr, byte>(), Properties.Resources.d_4.ToImage<Bgr, byte>(), Properties.Resources.d_5.ToImage<Bgr, byte>(),
        Properties.Resources.d_6.ToImage<Bgr, byte>(), Properties.Resources.d_7.ToImage<Bgr, byte>(), Properties.Resources.d_8.ToImage<Bgr, byte>(),
        Properties.Resources.d_9.ToImage<Bgr, byte>(), Properties.Resources.d_m.ToImage<Bgr, byte>(),Properties.Resources.d_x.ToImage<Bgr, byte>(),Properties.Resources.d_s.ToImage<Bgr, byte>()
        };

        public static Bitmap 二值化放大截图(Point P1, Point P2, string col, int 倍数, int 阈值)
        {
            return 二值化放大截图(P1.X, P1.Y, P2.X, P2.Y, col, 倍数, 阈值);
        }

        public static Bitmap 二值化放大截图(int L, int T, int R, int B, string col, int 倍数, int 阈值)
        {
            using (var bim = 截图(L, T, R, B))
            {
                using (var bim2 = 二值化图像(bim, HexToBgr(col), 阈值))
                {
                    return 按比例放大图像(bim2.Mat, 倍数).ToBitmap();
                }
            }
        }


        public static Bitmap 截图(Point P1, Point P2)
        {
            return 截图(Rectangle.FromLTRB(P1.X, P1.Y, P2.X, P2.Y));
        }

        public static Bitmap 截图(int L, int T, int R, int B)
        {
            return 截图(Rectangle.FromLTRB(L, T, R, B));
        }

        public static Bitmap 截图(Rectangle 范围)
        {
            Bitmap bmp = new Bitmap(范围.Width, 范围.Height);
            using (Graphics g = Graphics.FromImage(bmp))
            {
                g.CopyFromScreen(范围.Location, Point.Empty, 范围.Size);
            }
            return bmp;
        }

        public static Bgr HexToBgr(string hexColor)
        {
            Color color = ColorTranslator.FromHtml(hexColor);
            return new Bgr(color.B, color.G, color.R);
        }

        public static Hsv RgbToHsv(Bgr color)
        {
            double r = color.Red;
            double g = color.Green;
            double b = color.Blue;

            double rNorm = r / 255.0;
            double gNorm = g / 255.0;
            double bNorm = b / 255.0;

            double max = Math.Max(rNorm, Math.Max(gNorm, bNorm));
            double min = Math.Min(rNorm, Math.Min(gNorm, bNorm));
            double delta = max - min;

            // 计算色相
            double h;
            if (delta == 0)
            {
                h = 0;
            }
            else if (max == rNorm)
            {
                h = (gNorm - bNorm) / delta % 6;
            }
            else if (max == gNorm)
            {
                h = (bNorm - rNorm) / delta + 2;
            }
            else
            {
                h = (rNorm - gNorm) / delta + 4;
            }

            h = h * 60;
            if (h < 0) h += 360;

            // 计算饱和度
            double s = (max == 0) ? 0 : (delta / max);

            // 计算值
            double v = max;

            return new Hsv { h = h, s = s, v = v };
        }

        public static Image<Bgr, byte> 二值化图像(Bitmap image, string bgr, int 阈值)
        {
            return 二值化图像(image.ToImage<Bgr, byte>(), HexToBgr(bgr), 阈值);
        }

        public static Image<Bgr, byte> 二值化图像(Bitmap image, Bgr bgr, int 阈值)
        {
            return 二值化图像(image.ToImage<Bgr, byte>(), bgr, 阈值);
        }

        public static Image<Bgr, byte> 二值化图像(Image<Bgr, byte> image, string bgr, int 阈值)
        {
            return 二值化图像(image, HexToBgr(bgr), 阈值);
        }

        public static Image<Bgr, byte> 二值化图像(Image<Bgr, byte> image, Bgr bgr, int 阈值)
        {
            Image<Bgr, byte> 全黑图像 = new Image<Bgr, byte>(image.Size);
            全黑图像.SetValue(new Bgr(255, 255, 255));
            var 白色 = new Bgr(0, 0, 0);
            // 遍历每个像素点
            for (int y = 0; y < image.Rows; y++)
            {
                for (int x = 0; x < image.Cols; x++)
                {
                    Bgr pixel = image[y, x];
                    if (是匹配的颜色(pixel, bgr, 阈值))
                    {
                        全黑图像[y, x] = 白色;
                    }
                }
            }
            return 全黑图像;
        }

        public static bool 是匹配的颜色(Bgr pixelColor, Bgr targetColor, int tolerance = 5)
        {
            return Math.Abs(pixelColor.Blue - targetColor.Blue) < tolerance &&
                   Math.Abs(pixelColor.Green - targetColor.Green) < tolerance &&
                   Math.Abs(pixelColor.Red - targetColor.Red) < tolerance;
        }

        public static bool 比较两张图像是否相同(Mat image1, Mat image2)
        {
            using (Mat diff = new Mat())
            {
                CvInvoke.AbsDiff(image1, image2, diff);
                using (Mat grayDiff = new Mat())
                {
                    CvInvoke.CvtColor(diff, grayDiff, ColorConversion.Bgr2Gray);
                    int diffPixels = CvInvoke.CountNonZero(grayDiff);
                    return diffPixels == 0;
                }
            }
        }

        public static Point 在范围内找模板返回中心(Point P1, Point P2, Bitmap 模板, double 阈值)
        {
            var R = 内部();
            return R == Point.Empty ? R : new Point(P1.X + R.X + 模板.Width / 2, P1.Y + R.Y + 模板.Height / 2);
            Point 内部()
            {
                var parBitmap = 截图(P1, P2).ToImage<Bgr, byte>();
                var subBitmap = 模板.ToImage<Bgr, byte>();
                var r = parBitmap.MatchTemplate(subBitmap, TemplateMatchingType.CcoeffNormed);
                r.MinMax(out _, out double[] maxValues, out _, out Point[] maxLocations);
                r.Dispose();
                subBitmap.Dispose();
                parBitmap.Dispose();
                for (int i = 0; i < maxValues.Length; i++)
                {
                    if (maxValues[i] >= 阈值)
                    {
                        return maxLocations[i];
                    }
                }
                return Point.Empty;
            }
        }

        public static Image<Bgr, byte> 删除右侧部分(Image<Bgr, byte> 输入图像, int 删除宽度)
        {
            // 计算剩余宽度
            int 剩余宽度 = 输入图像.Width - 删除宽度;
            // 确保剩余宽度有效
            if (剩余宽度 <= 0)
            {
                Console.WriteLine("删除宽度过大，将导致无剩余图像");
                return null;
            }

            // 创建裁剪区域
            Rectangle 裁剪区域 = new Rectangle(0, 0, 剩余宽度, 输入图像.Height);
            // 裁剪图像
            return 输入图像.Copy(裁剪区域);
        }

        public static Image<Bgr, byte> 根据颜色匹配裁剪尾部图像(Image<Bgr, byte> 输入图像, Bgr 目标颜色, int 容差范围 = 30)
        {

            // 定义监测窗口尺寸
            int 窗口宽度 = 6;
            int 窗口高度 = 输入图像.Height;

            // 初始化结果位置
            int 最后有效位置 = 0;

            // 从左到右以窗口宽度为步长滑动监测窗口
            for (int x = 0; x <= 输入图像.Width - 窗口宽度; x += 窗口宽度)
            {
                bool 找到匹配 = false;

                // 检查当前窗口内的所有像素
                for (int wx = 0; wx < 窗口宽度; wx++)
                {
                    for (int wy = 0; wy < 窗口高度; wy++)
                    {
                        int 当前X = x + wx;
                        int 当前Y = wy;

                        // 获取当前像素颜色
                        Bgr 当前像素颜色 = 输入图像[当前Y, 当前X];

                        // 计算颜色差异
                        if (是匹配的颜色(当前像素颜色, 目标颜色, 容差范围))
                        {
                            找到匹配 = true;
                            break;
                        }
                    }
                    if (找到匹配) break;
                }
                // 如果窗口内有任何像素匹配目标颜色，记录位置并继续下一个窗口
                if (找到匹配)
                {
                    最后有效位置 = x + 窗口宽度;
                }
                else
                {
                    // 一旦发现不包含匹配像素的窗口，立即停止搜索
                    break;
                }
            }

            // 如果找到了有效的位置，裁剪图像
            if (最后有效位置 > 0)
            {
                Rectangle 裁剪区域 = new Rectangle(0, 0, 最后有效位置 + 1, 输入图像.Height);
                return 输入图像.Copy(裁剪区域);
            }

            return null;
        }

        public static bool 大图中有小图(Bitmap 大图像, Bitmap 小图像, double 阈值)
        {
            return 查找匹配结果(大图像.ToImage<Bgr, byte>(), 小图像.ToImage<Bgr, byte>(), 阈值);
        }

        public static bool 大图中有在小图(Image<Bgr, byte> 大图像, Image<Bgr, byte> 小图像, double 阈值)
        {
            return 查找匹配结果(大图像, 小图像, 阈值);
        }

        public static bool 大图中有在小图(Mat 大图像, Mat 小图像, double 阈值)
        {
            return 查找匹配结果(大图像.ToImage<Bgr, byte>(), 小图像.ToImage<Bgr, byte>(), 阈值);
        }

        public static Bitmap 按比例放大图像(Bitmap 图像, int 倍数 = 2)
        {
            return 按比例放大图像(图像.ToMat(), 倍数).ToBitmap();
        }

        public static Image<Bgr, byte> 按比例放大图像(Image<Bgr, byte> 图像, int 倍数 = 2)
        {
            return 按比例放大图像(图像.Mat, 倍数).ToImage<Bgr, byte>();
        }

        public static Mat 按比例放大图像(Mat 图像, int 倍数 = 2)
        {
            var 新高度 = 图像.Height * 倍数;
            var 新宽度 = 图像.Width * 倍数;
            using (var img = 图像.ToImage<Bgr, byte>())
            {
                return img.Resize(新宽度, 新高度, Inter.Nearest).Mat;
            }
        }

        static bool 查找匹配结果(Image<Bgr, byte> parBitmap, Image<Bgr, byte> subBitmap, double threshold)
        {
            var r = parBitmap.MatchTemplate(subBitmap, TemplateMatchingType.CcoeffNormed);
            r.MinMax(out _, out double[] maxValues, out _, out Point[] maxLocations);
            r.Dispose();
            for (int i = 0; i < maxValues.Length; i++)
            {
                if (maxValues[i] >= threshold)
                {
                    return true;
                }
            }
            return false;
        }

        public static Tuple<Image<Bgr, byte>, Image<Bgr, byte>> 按列分割图片(Bitmap img, int w, string col, double 容差)
        {
            return 按列分割图片(img.ToImage<Bgr, byte>(), w, col, 容差);
        }

        public static Tuple<Image<Bgr, byte>, Image<Bgr, byte>> 按列分割图片(Image<Bgr, byte> img, int w, string col, double 容差)
        {
            var bgr = HexToBgr(col);
            var h = img.Height;
            var t = 扫描图像发现空格分隔为两部分(img, w, bgr, 容差);
            return new Tuple<Image<Bgr, byte>, Image<Bgr, byte>>(t.Item1 != null ? t.Item1 : null, t.Item2 != null ? t.Item2 : null);
        }

        public static List<Image<Bgr, byte>> 分割为行(Bitmap 源图像, int 子图像高度 = 18)
        {
            return 分割为行(源图像.ToImage<Bgr, byte>(), 子图像高度);
        }

        public static List<Image<Bgr, byte>> 分割为行(Image<Bgr, byte> 源图像, int 子图像高度 = 18)
        {
            if (源图像 == null)
                throw new ArgumentNullException("源图像");

            if (子图像高度 <= 0)
                throw new ArgumentException("子图像高度必须大于0", nameof(子图像高度));

            var 结果列表 = new List<Image<Bgr, byte>>();
            int 当前位置 = 0;

            // 按固定高度循环分割图像
            while (当前位置 < 源图像.Height)
            {
                // 计算当前子图像的实际高度（处理剩余部分）
                int 实际高度 = Math.Min(子图像高度, 源图像.Height - 当前位置);

                // 创建感兴趣区域（注意：横向分割修改Y坐标和高度）
                Rectangle 区域 = new Rectangle(0, 当前位置, 源图像.Width, 实际高度);

                // 提取子图像并添加到结果列表
                using (Image<Bgr, byte> 子图像 = 源图像.GetSubRect(区域))
                {
                    结果列表.Add(子图像.Copy());
                }

                // 更新当前位置
                当前位置 += 子图像高度;
            }
            return 结果列表;
        }

        public static Tuple<Image<Bgr, byte>, Image<Bgr, byte>> 扫描图像发现空格分隔为两部分(Image<Bgr, byte> 原图像, int 扫描器宽度, Bgr 目标颜色, double 容差)
        {
            if (原图像 == null)
                throw new ArgumentNullException("sourceImage");

            // 验证图像尺寸是否符合要求
            if (原图像.Width < 扫描器宽度)
                throw new ArgumentException($"图像尺寸不符合要求:  宽度>={扫描器宽度}");

            int splitColumn = 查找分割列(原图像, 扫描器宽度, 目标颜色, 容差);
            if (splitColumn > 0)
            {
                return 按分割点分割图像(原图像, splitColumn, 扫描器宽度, 目标颜色);
            }

            // 如果没有找到分割点，返回原图和null
            return new Tuple<Image<Bgr, byte>, Image<Bgr, byte>>(原图像, null);
        }

        private static int 查找分割列(Image<Bgr, byte> 原图像, int 扫描器宽度, Bgr 目标颜色, double 容差)
        {
            var windowSize = new Size(扫描器宽度, 原图像.Height);
            // 滑动窗口扫描
            for (int col = 0; col <= 原图像.Width - windowSize.Width; col += windowSize.Width)
            {
                bool colorFound = false;

                // 扫描窗口内的所有像素
                for (int winCol = 0; winCol < windowSize.Width; winCol++)
                {
                    for (int winRow = 0; winRow < windowSize.Height; winRow++)
                    {
                        int imgCol = col + winCol;
                        int imgRow = winRow;

                        Bgr pixelColor = 原图像[imgRow, imgCol];
                        if (是匹配的颜色(pixelColor, 目标颜色))
                        {
                            colorFound = true;
                            break;
                        }
                    }

                    if (colorFound) break; // 找到颜色后跳出窗口列循环
                }

                // 如果窗口内没有找到目标颜色，返回当前位置作为分割点
                if (!colorFound)
                {
                    return col; // 返回窗口中心位置作为分割点
                }
            }

            return -1; // 没有找到合适的分割点
        }

        private static Tuple<Image<Bgr, byte>, Image<Bgr, byte>> 按分割点分割图像(Image<Bgr, byte> 原图像, int 分割列, int 扫描器宽度, Bgr 目标颜色)
        {
            int widthLeft = 分割列;
            int widthRight = 原图像.Width - 分割列 - 扫描器宽度;

            Image<Bgr, byte> leftImage = new Image<Bgr, byte>(widthLeft, 原图像.Height);
            Image<Bgr, byte> rightImage = new Image<Bgr, byte>(widthRight, 原图像.Height);

            using (Image<Bgr, byte> roiLeft = 原图像.GetSubRect(new Rectangle(0, 0, widthLeft, 原图像.Height)))
            using (Image<Bgr, byte> roiRight = 原图像.GetSubRect(new Rectangle(分割列 + 扫描器宽度, 0, widthRight, 原图像.Height)))
            {
                try
                {
                    CvInvoke.cvCopy(roiLeft.Ptr, leftImage.Ptr, IntPtr.Zero);
                    CvInvoke.cvCopy(roiRight.Ptr, rightImage.Ptr, IntPtr.Zero);
                }
                catch (Exception e)
                {
                    throw e;
                }
            }
            var t = 根据颜色匹配裁剪尾部图像(rightImage, 目标颜色);
            return new Tuple<Image<Bgr, byte>, Image<Bgr, byte>>(leftImage, t);
        }

        public static Image<Bgr, byte> 横向拼接(Image<Bgr, byte> 图片1, Image<Bgr, byte> 图片2)
        {
            try
            {
                // 创建拼接后的新图片（宽度为两张图片之和，高度不变）
                int 新宽度 = 图片1.Cols + 图片2.Cols;
                int 新高度 = 图片1.Rows;
                Image<Bgr, byte> 结果图片 = new Image<Bgr, byte>(新宽度, 新高度);
                // 将第一张图片复制到结果图片的左侧
                Image<Bgr, byte> 左侧区域 = 结果图片.GetSubRect(new Rectangle(0, 0, 图片1.Cols, 图片1.Rows));
                图片1.CopyTo(左侧区域);
                // 将第二张图片复制到结果图片的右侧
                Image<Bgr, byte> 右侧区域 = 结果图片.GetSubRect(new Rectangle(图片1.Cols, 0, 图片2.Cols, 图片2.Rows));
                图片2.CopyTo(右侧区域);
                return 结果图片;
            }
            catch (Exception 异常)
            {
                Console.WriteLine($"拼接过程中发生错误: {异常.Message}");
                return null;
            }
        }

        /// <summary>
        /// 根据指定颜色分割图像
        /// </summary>
        public static Tuple<Image<Bgr, byte>, Image<Bgr, byte>> 按颜色分割图像(Image<Bgr, byte> 输入图像, Bgr 目标颜色, int 最小连续列数 = 8, int 容差 = 20)
        {
            // 查找分割位置
            int 分割位置 = 查找分割位置(输入图像, 目标颜色, 最小连续列数, 容差);

            if (分割位置 <= 0)
                return Tuple.Create<Image<Bgr, byte>, Image<Bgr, byte>>(输入图像, null);

            // 创建左右两部分图像
            Image<Bgr, byte> 左半图像 = 创建图像部分(输入图像, 0, 分割位置);

            int 右半起始 = 分割位置 + 最小连续列数;
            Image<Bgr, byte> 右半图像 = 创建图像部分(输入图像, 右半起始, 输入图像.Width - 右半起始);

            return Tuple.Create(左半图像, 右半图像);
        }
        /// <summary>
        /// 查找图像中符合条件的分割位置
        /// </summary>
        private static int 查找分割位置(Image<Bgr, byte> 图像, Bgr 目标颜色, int 最小连续列数, int 容差)
        {
            int 连续未出现列数 = 0;

            for (int 列号 = 0; 列号 < 图像.Width; 列号++)
            {
                bool 找到颜色 = false;
                for (int 行号 = 0; 行号 < 图像.Height; 行号++)
                {
                    Bgr 当前像素颜色 = 图像[行号, 列号];
                    if (是匹配的颜色(当前像素颜色, 目标颜色, 容差))
                    {
                        找到颜色 = true;
                        break;
                    }
                }
                if (!找到颜色)
                {
                    连续未出现列数++;
                    if (连续未出现列数 >= 最小连续列数)
                    {
                        return 列号 - 连续未出现列数 + 1;
                    }
                }
                else
                {
                    连续未出现列数 = 0;
                }
            }

            return -1;
        }
        /// <summary>
        /// 创建图像的一部分
        /// </summary>
        private static Image<Bgr, byte> 创建图像部分(Image<Bgr, byte> 源图像, int 起始位置, int 宽度)
        {
            if (起始位置 < 0 || 宽度 <= 0 || 起始位置 + 宽度 > 源图像.Width)
                throw new ArgumentException("无效的图像区域参数");

            Rectangle 区域 = new Rectangle(起始位置, 0, 宽度, 源图像.Height);
            return 源图像.GetSubRect(区域).Copy(); // 返回克隆以确保数据独立
        }
        /// <summary>
        /// 对图像进行分块模板匹配，支持自动填充黑色像素
        /// </summary>
        public static List<int> 分块模板匹配(Image<Bgr, byte> 输入图像, int 块宽度 = 6, bool 自动填充 = true)
        {
            // 如果启用自动填充且宽度不是块宽度的整数倍，则填充图像
            Image<Bgr, byte> 处理后图像 = 输入图像;
            if (自动填充 && 输入图像.Width % 块宽度 != 0)
            {
                处理后图像 = 填充图像(输入图像, 块宽度);
            }

            var 匹配结果列表 = new List<int>();
            int 块数量 = 处理后图像.Width / 块宽度;

            // 对图像进行分块处理
            for (int 块索引 = 0; 块索引 < 块数量; 块索引++)
            {
                int 起始列 = 块索引 * 块宽度;
                // 提取当前块
                Rectangle 块区域 = new Rectangle(起始列, 0, 块宽度, 8);
                using (Image<Bgr, byte> 当前块 = 处理后图像.GetSubRect(块区域).Copy())
                {
                    // 与每个模板进行匹配，记录最高匹配值
                    double 最高匹配值 = 0;
                    int 当前索引 = -1;
                    for (var i = 0; i < 大地图数字数组.Length; i++)
                    {
                        var 模板 = 大地图数字数组[i];
                        double 匹配值 = 模板匹配(当前块, 模板);
                        if (匹配值 > 最高匹配值)
                        {
                            if (i == 11)
                            {
                                goto 发现斜杠停止_;
                            }
                            最高匹配值 = 匹配值;
                            当前索引 = i;
                        }
                    }
                    if (最高匹配值 > 0.5 && 当前索引 != 12) 匹配结果列表.Add(当前索引);
                }
            }
        发现斜杠停止_:
            // 如果创建了临时填充图像，释放资源
            if (处理后图像 != 输入图像)
            {
                处理后图像.Dispose();
            }
            return 匹配结果列表;
        }

        /// <summary>
        /// 在图像右侧填充黑色像素，使其宽度达到块宽度的整数倍
        /// </summary>
        private static Image<Bgr, byte> 填充图像(Image<Bgr, byte> 源图像, int 块宽度)
        {
            int 目标宽度 = ((源图像.Width / 块宽度) + 1) * 块宽度;
            Image<Bgr, byte> 填充后图像 = new Image<Bgr, byte>(目标宽度, 8);

            using (Image<Bgr, byte> 左侧区域 = 填充后图像.GetSubRect(new Rectangle(0, 0, 源图像.Width, 8)))
            {
                源图像.CopyTo(左侧区域);
            }

            return 填充后图像;
        }

        /// <summary>
        /// 模板匹配方法，返回匹配度
        /// </summary>
        public static double 模板匹配(Image<Bgr, byte> 源图像, Image<Bgr, byte> 模板)
        {
            using (Mat result = new Mat())
            {
                CvInvoke.MatchTemplate(源图像, 模板, result, TemplateMatchingType.CcoeffNormed);
                double minVal = -1, maxVal = -1;
                Point minLoc = Point.Empty, maxLoc = Point.Empty;
                CvInvoke.MinMaxLoc(result, ref minVal, ref maxVal, ref minLoc, ref maxLoc);
                return maxVal;
            }
        }
    }
}
